//
// Copyright (C) 2008 Irene Ruengeler
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//

#include <omnetpp.h>
#include "SCTPSocket.h"
#include "SCTP.h"
#include "SCTPAssociation.h"


SCTPSocket::SCTPSocket()
{
	assocId = SCTP::getNewConnId();
	sockstate = NOT_BOUND;
	
	localPrt = remotePrt = 0;
	cb = NULL;
	yourPtr = NULL;
	
	gateToSctp = NULL;
	lastStream=-1;
	sctpEV3<<"sockstate="<<sockstate<<"\n";
}


SCTPSocket:: ~SCTPSocket() 
{
	localAddresses.clear();
}

const char *SCTPSocket::stateName(int state)
{
#define CASE(x) case x: s=#x; break
	const char *s = "unknown";
	switch (state)
	{
		CASE(NOT_BOUND);
		CASE(CLOSED);
		CASE(LISTENING);
		CASE(CONNECTING);
		CASE(CONNECTED);
		CASE(PEER_CLOSED);
		CASE(LOCALLY_CLOSED);
		CASE(SOCKERROR);
	}
	return s;
#undef CASE
}

void SCTPSocket::sendToSCTP(cPacket *msg)
{
	if (!gateToSctp)
		opp_error("SCTPSocket: setOutputGate() must be invoked before socket can be used");
	check_and_cast<cSimpleModule *>(gateToSctp->getOwnerModule())->send(msg, gateToSctp);
}

void SCTPSocket::bind(int lPort)
{
	if (sockstate!=NOT_BOUND)
		opp_error("SCTPSocket::bind(): socket already bound");
	localAddresses.push_back(IPvXAddress("0.0.0.0"));
	localPrt = lPort;
	sockstate = CLOSED;
}

void SCTPSocket::bind(IPvXAddress lAddr, int lPort)
{
	 
	sctpEV3<<"bind address "<<lAddr<<"\n";
	
	if (sockstate!=NOT_BOUND)
		opp_error("SCTPSocket::bind(): socket already bound");
	localAddresses.push_back(lAddr);
	localPrt = lPort;
	sockstate = CLOSED;
}

void SCTPSocket::addAddress(IPvXAddress addr)
{
	sctpEV3<<"add address "<<addr<<"\n";
	localAddresses.push_back(addr);
}

void SCTPSocket::bindx(AddressVector lAddresses, int lPort)
{
	IPvXAddress lAddr;
	for (AddressVector::iterator i=lAddresses.begin(); i!=lAddresses.end(); ++i)
	{
		ev<<"bindx: bind address "<<(*i)<<"\n";
		localAddresses.push_back((*i));
	}
	localPrt = lPort;
	sockstate = CLOSED;
}

void SCTPSocket::listen(bool fork, uint32 requests, uint32 messagesToPush)
{
	if (sockstate!=CLOSED)
		opp_error(sockstate==NOT_BOUND ? "SCTPSocket: must call bind() before listen()"
		                               : "SCTPSocket::listen(): connect() or listen() already called");
	
	cPacket *msg = new cPacket("PassiveOPEN", SCTP_C_OPEN_PASSIVE);
	
	SCTPOpenCommand *openCmd = new SCTPOpenCommand();
	//openCmd->setLocalAddr(localAddr);
	openCmd->setLocalAddresses(localAddresses);
	openCmd->setLocalPort(localPrt);
	openCmd->setAssocId(assocId);
	openCmd->setFork(fork);
	openCmd->setOutboundStreams(outboundStreams);
	openCmd->setNumRequests(requests);
	openCmd->setMessagesToPush(messagesToPush);
	msg->setControlInfo(openCmd);
	 
	sctpEV3<<"Assoc "<<assocId<<"::send PassiveOPEN to SCTP from socket:listen \n";
	
	sendToSCTP(msg);
	sockstate = LISTENING;
}

void SCTPSocket::connect(IPvXAddress remoteAddress, int32 remotePort, uint32 numRequests)
{
sctpEV3<<"Socket connect. Assoc="<<assocId<<", sockstate="<<sockstate<<"\n";	 
	if (sockstate!=NOT_BOUND && sockstate!=CLOSED)
		opp_error( "SCTPSocket::connect(): connect() or listen() already called");
	cPacket *msg = new cPacket("Associate", SCTP_C_ASSOCIATE);
	remoteAddr = remoteAddress;
	remotePrt = remotePort;
	SCTPOpenCommand *openCmd = new SCTPOpenCommand();
	openCmd->setAssocId(assocId);
	openCmd->setLocalAddresses(localAddresses);
	openCmd->setLocalPort(localPrt);
	openCmd->setRemoteAddr(remoteAddr);
	openCmd->setRemotePort(remotePrt);
	openCmd->setOutboundStreams(outboundStreams);
	openCmd->setNumRequests(numRequests);
	msg->setControlInfo(openCmd);
	sendToSCTP(msg);
	sockstate = CONNECTING;
}

void SCTPSocket::connectx(AddressVector remoteAddressList, int32 remotePort, uint32 numRequests)
{
	sctpEV3<<"Socket connectx. Assoc="<<assocId<<", sockstate="<<sockstate<<"\n";	 
	if (sockstate!=NOT_BOUND && sockstate!=CLOSED)
		opp_error( "SCTPSocket::connect(): connect() or listen() already called");
	cPacket *msg = new cPacket("Associate", SCTP_C_ASSOCIATE);
	remoteAddresses = remoteAddressList;
	remoteAddr = remoteAddresses.front();
	remotePrt = remotePort;
	SCTPOpenCommand *openCmd = new SCTPOpenCommand();
	openCmd->setAssocId(assocId);
	openCmd->setLocalAddresses(localAddresses);
	openCmd->setLocalPort(localPrt);
	openCmd->setRemoteAddr(remoteAddr);
	openCmd->setRemoteAddresses(remoteAddresses);
	openCmd->setRemotePort(remotePrt);
	openCmd->setOutboundStreams(outboundStreams);
	openCmd->setNumRequests(numRequests);
	msg->setControlInfo(openCmd);
	sendToSCTP(msg);
	sockstate = CONNECTING;
}

void SCTPSocket::send(cPacket *msg, bool last, bool primary)
{	
	if (sockstate!=CONNECTED && sockstate!=CONNECTING && sockstate!=PEER_CLOSED)
		opp_error("SCTPSocket::send(): not connected or connecting");
	SCTPSendCommand *cmd = new SCTPSendCommand();	
	cmd->setAssocId(assocId);
	if (msg->getKind() == SCTP_C_SEND_ORDERED)
		cmd->setSendUnordered(COMPLETE_MESG_ORDERED);
	else
		cmd->setSendUnordered(COMPLETE_MESG_UNORDERED);
	lastStream=(lastStream+1)%outboundStreams;
	cmd->setSid(lastStream);
	cmd->setLast(last);
	cmd->setPrimary(primary);
	msg->setKind(SCTP_C_SEND);
	msg->setControlInfo(cmd);
	sendToSCTP(msg);
}


void SCTPSocket::sendNotification(cPacket *msg)
{	 
	if (sockstate!=CONNECTED && sockstate!=CONNECTING && sockstate!=PEER_CLOSED)
		opp_error("SCTPSocket::send(): not connected or connecting");
	sendToSCTP(msg);
}

void SCTPSocket::sendRequest(cPacket *msg)
{
	sendToSCTP(msg);
	
}

void SCTPSocket::close()
{
	sctpEV3<<"SCTPSocket: close\n";

	cPacket *msg = new cPacket("CLOSE", SCTP_C_CLOSE);
	SCTPCommand *cmd = new SCTPCommand();
	cmd->setAssocId(assocId);
	msg->setControlInfo(cmd);
	sendToSCTP(msg);
	sockstate = sockstate==CONNECTED ? LOCALLY_CLOSED : CLOSED;
}

void SCTPSocket::shutdown()
{
	ev<<"SCTPSocket: shutdown\n";

	cPacket *msg = new cPacket("Shutdown", SCTP_C_SHUTDOWN);
	SCTPCommand *cmd = new SCTPCommand();
	cmd->setAssocId(assocId);
	msg->setControlInfo(cmd);
	sendToSCTP(msg);
}

void SCTPSocket::abort()
{	
	if (sockstate!=NOT_BOUND && sockstate!=CLOSED && sockstate!=SOCKERROR)
	{
		cPacket *msg = new cPacket("ABORT", SCTP_C_ABORT);
		SCTPCommand *cmd = new SCTPCommand();
		 
		//sctpEV3<<"Message cmd="<<&cmd<<"\n";
		
		cmd->setAssocId(assocId);
		msg->setControlInfo(cmd);	
		sendToSCTP(msg);
	}
	sockstate = CLOSED;
}

void SCTPSocket::requestStatus()
{
	cPacket *msg = new cPacket("STATUS", SCTP_C_STATUS);
	SCTPCommand *cmd = new SCTPCommand();	
	cmd->setAssocId(assocId);
	msg->setControlInfo(cmd);
	sendToSCTP(msg);
}

bool SCTPSocket::belongsToSocket(cPacket *msg)
{  
	bool ret= dynamic_cast<SCTPCommand *>(msg->getControlInfo()) &&
           ((SCTPCommand *)(msg->getControlInfo()))->getAssocId()==assocId;
	sctpEV3<<"assoc="<<((SCTPCommand *)(msg->getControlInfo()))->getAssocId()<<"\n";
	return ret;
}

bool SCTPSocket::belongsToAnySCTPSocket(cPacket *msg)
{
	return dynamic_cast<SCTPCommand *>(msg->getControlInfo());
}

void SCTPSocket::setCallbackObject(CallbackInterface *callback, void *yourPointer)
{
	cb = callback;
	yourPtr = yourPointer;
}

void SCTPSocket::processMessage(cPacket *msg)
{	
	SCTPStatusInfo *status;
	switch (msg->getKind())
	{
		case SCTP_I_DATA:
			 
			sctpEV3<<"SCTP_I_DATA\n";
			
			if (cb) 
				cb->socketDataArrived(assocId, yourPtr, msg, false);
			break;
		case SCTP_I_DATA_NOTIFICATION:
			 
			sctpEV3<<"SCTP_I_NOTIFICATION\n";
			
			if (cb) 
				cb->socketDataNotificationArrived(assocId, yourPtr, msg);	
			break;
		case SCTP_I_SEND_MSG:
			if (cb)
				cb->sendRequestArrived();
			break;
		case SCTP_I_ESTABLISHED:
		{	 
			sockstate = CONNECTED;
			SCTPConnectInfo *connectInfo = check_and_cast<SCTPConnectInfo *>(msg->removeControlInfo());
			localAddr = connectInfo->getLocalAddr();
			remoteAddr = connectInfo->getRemoteAddr();
			localPrt = connectInfo->getLocalPort();
			remotePrt = connectInfo->getRemotePort();;
			fsmStatus = connectInfo->getStatus();
			inboundStreams = connectInfo->getInboundStreams();
			outboundStreams = connectInfo->getOutboundStreams();
			
			if (cb)
				cb->socketEstablished(assocId, yourPtr);
			delete connectInfo;
			break;
		}
		case SCTP_I_PEER_CLOSED:
			sctpEV3<<"peer closed\n";
			sockstate = sockstate==CONNECTED ? PEER_CLOSED : CLOSED;

			if (cb)
				cb->socketPeerClosed(assocId, yourPtr);
			break;
		case SCTP_I_ABORT:
		case SCTP_I_CONN_LOST:
		case SCTP_I_CLOSED:

			sockstate = CLOSED;

			if (cb)
				cb->socketClosed(assocId, yourPtr);
			break;
		case SCTP_I_CONNECTION_REFUSED:
		case SCTP_I_CONNECTION_RESET:
		case SCTP_I_TIMED_OUT:
			sockstate = SOCKERROR;
			if (cb)
				cb->socketFailure(assocId, yourPtr, msg->getKind());

			break;
		case SCTP_I_STATUS:
			status = check_and_cast<SCTPStatusInfo *>(msg->removeControlInfo());

			if (cb)
				cb->socketStatusArrived(assocId, yourPtr, status);
			delete status;
			break;
		case SCTP_I_SHUTDOWN_RECEIVED:
			sctpEV3<<"SCTP_I_SHUTDOWN_RECEIVED\n";
		if (cb)
				cb->shutdownReceivedArrived(assocId);
			break;
		case SCTP_I_SENDQUEUE_FULL:
			if (cb)
				cb->sendqueueFullArrived(assocId);
			break;
		default:
			opp_error("SCTPSocket: invalid msg kind %d, one of the SCTP_I_xxx constants expected", msg->getKind());
	}

	delete msg;
}

